package cn.com.easy.pay.weixinpay.utils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.lang3.StringUtils;
import org.xml.sax.SAXException;

import cn.com.easy.utils.ReflectUtils;

/**
 * 签名工具类<br/>
 * User: rizenguo Date: 2014/10/29 Time: 15:23
 */
public class Signature {

	public static void main(String[] args) {
		// String a =
		// "appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA";
		// String signTemp = a + "&key=192006250b4c09247ec02edce69f6a2d";
		// String sign =
		// MD5.MD5Encode("appid=wx75bf7b090e900622&body=%E4%BF%9D%E9%9A%9C%E7%BD%91-%E5%AD%98%E4%B8%80%E4%B8%87%E6%8A%B5%E4%B8%A4%E4%B8%87&mch_id=1475848302&nonce_str=pqTGaEX5FlBH36Ke&notify_url=http%3A%2F%2F192.168.26.150%2Fweixinpay%2Fnotify&out_trade_no=255801494294658350&sign_type=MD5&spbill_create_ip=192.168.26.150&total_fee=1&trade_type=APP&key=674014b1fabdb4e761a4934eb61ac66f").toUpperCase();//
		// "9A0A8659F005D6984697E2CA0A9CF3B7"
		// System.out.println(sign);
		//
		// System.out.println("674014b1fabdb4e761a4934eb61ac66f".length());

		// UnifiedOrderParam unifiedOrderParam = new UnifiedOrderParam();
		// unifiedOrderParam.setAppid("wxd930ea5d5a258f4f");
		// unifiedOrderParam.setBody("test");
		// unifiedOrderParam.setDevice_info("1000");
		// unifiedOrderParam.setMch_id("10000100");
		// unifiedOrderParam.setNonce_str("ibuaiVcKdpRxkhJA");
		//
		// String sign = Signature.getSign(unifiedOrderParam, "MD5",
		// "192006250b4c09247ec02edce69f6a2d"); //
		// "9A0A8659F005D6984697E2CA0A9CF3B7"
		// System.out.println(sign);

	}

	/**
	 * * 生成签名 <br/>
	 * https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3
	 * 
	 * @param signParam
	 * @param signType
	 * @param key
	 * @return
	 * @throws IllegalAccessException
	 * @throws UnsupportedEncodingException
	 * @author nibili 2017年5月8日
	 */
	public static String getSign(Object object, String signType, String key) throws IllegalAccessException, UnsupportedEncodingException {
		Map<String, String> paramMap = ReflectUtils.reflectObjectFieldsToMap(object);
		// 参数值url
		String urlParamString = formatUrlMap(paramMap, false) + "&key=" + key;
		// 签名
		String sign = "";
		if (StringUtils.equals(signType, "MD5") == true) {
			// md5签名
			sign = MD5.MD5Encode(urlParamString).toUpperCase();
		}
		return sign;
	}

	/**
	 * 转成url参数字符串
	 * 
	 * @param paraMap
	 * @param urlEncode
	 * @return
	 * @author nibili 2017年5月8日
	 */
	private static String formatUrlMap(Map<String, String> paraMap, boolean urlEncode) {
		String buff = "";
		Map<String, String> tmpMap = paraMap;
		try {
			List<Map.Entry<String, String>> infoIds = new ArrayList<Map.Entry<String, String>>(tmpMap.entrySet());
			// 对所有传入参数按照字段名的 ASCII 码从小到大排序（字典序）
			Collections.sort(infoIds, new Comparator<Map.Entry<String, String>>() {
				@Override
				public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) {
					return (o1.getKey()).toString().compareTo(o2.getKey());
				}
			});
			// 构造URL 键值对的格式
			StringBuilder buf = new StringBuilder();
			for (Map.Entry<String, String> item : infoIds) {
				String key = item.getKey();
				String val = item.getValue();
				if (StringUtils.isNoneBlank(key, val) == true && "sign".equals(key) == false) {
					if (null != val && !"".equals(val)) {
						if (urlEncode) {
							val = URLEncoder.encode(val, "utf-8");
						}
						if (StringUtils.equals("packageValue", key) == true) {
							key = "package";
						}
						buf.append(key + "=" + val + "&");
					}
				}
			}
			buff = buf.toString();
			if (buff.isEmpty() == false) {
				buff = buff.substring(0, buff.length() - 1);
			}
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
		return buff;
	}

	/**
	 * 获取签名
	 * 
	 * @param map
	 * @param appKey
	 * @return
	 * @author nibili 2017年5月11日
	 */
	public static String getSign(Map<String, Object> map, String signType, String appKey) {
		ArrayList<String> list = new ArrayList<String>();
		for (Map.Entry<String, Object> entry : map.entrySet()) {
			if (entry.getValue() != "") {
				list.add(entry.getKey() + "=" + entry.getValue() + "&");
			}
		}
		int size = list.size();
		String[] arrayToSort = list.toArray(new String[size]);
		Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < size; i++) {
			sb.append(arrayToSort[i]);
		}
		String result = sb.toString();
		result += "key=" + appKey;

		String sign = "";
		if (StringUtils.equals(signType, "MD5") == true) {
			sign = MD5.MD5Encode(result).toUpperCase();
		}
		return sign;
	}

	/**
	 * 从API返回的XML数据里面重新计算一次签名
	 * 
	 * @param responseString
	 *            API返回的XML数据
	 * @return 新鲜出炉的签名
	 * @throws ParserConfigurationException
	 * @throws IOException
	 * @throws SAXException
	 */
	public static String getSignFromResponseString(String responseString, String signType, String appKey) throws IOException, SAXException, ParserConfigurationException {

		Map<String, Object> map = XMLParser.getMapFromXML(responseString);
		// 清掉返回数据对象里面的Sign数据（不能把这个数据也加进去进行签名），然后用签名算法进行签名
		map.put("sign", "");
		// 将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
		return Signature.getSign(map, signType, appKey);
	}

	/**
	 * 检验API返回的数据里面的签名是否合法，避免数据在传输的过程中被第三方篡改
	 * 
	 * @param responseXmlString
	 *            API返回的XML数据字符串
	 * @return API签名是否合法
	 * @throws ParserConfigurationException
	 * @throws IOException
	 * @throws SAXException
	 */
	public static boolean checkIsSignValidFromResponseString(String responseXmlString, String signType, String appKey) throws ParserConfigurationException, IOException,
			SAXException {

		Map<String, Object> map = XMLParser.getMapFromXML(responseXmlString);

		String signFromAPIResponse = map.get("sign").toString();
		if (signFromAPIResponse == "" || signFromAPIResponse == null) {
			return false;
		}
		// 清掉返回数据对象里面的Sign数据（不能把这个数据也加进去进行签名），然后用签名算法进行签名
		map.put("sign", "");
		// 将API返回的数据根据用签名算法进行计算新的签名，用来跟API返回的签名进行比较
		String signForAPIResponse = Signature.getSign(map, signType, appKey);
		if (!signForAPIResponse.equals(signFromAPIResponse)) {
			// 签名验不过，表示这个API返回的数据有可能已经被篡改了
			return false;
		}
		return true;
	}

}
